package com.tengju.payment.infrastructure.shared.union;

import chinapay.Base64;
import chinapay.PrivateKey;
import chinapay.SecureLink;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.core.util.StrUtil;
import com.tengju.payment.domain.shard.UnionPayConstant;
import com.tengju.payment.infrastructure.external.dto.*;
import com.tengju.payment.infrastructure.shared.FileUtil;
import com.tengju.payment.infrastructure.shared.HttpClientUtil;
import com.tengju.payment.infrastructure.shared.InfraException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author: 李大仙
 * @date: 2021/6/22 5:18 下午
 */
@Slf4j
@Component
public class UnionPayTransferHelper {

    private static final String unionPayMerPrKPath = "unionPay/key";
    private static final String unionPayMerPrKName = "MerPrK.key";
    private static final String unionPayPgPubKPath = "unionPay/key";
    private static final String unionPayPgPubKName = "PgPubk.key";

    @Value("${union.pay.mer.pr.key}")
    private String unionPayMerPrKString;
    @Value("${union.pay.pg.pub.key}")
    private String unionPayPgPubKString;

    public UnionPaySinglePayQueryResponse singlePayQuery(UnionPaySinglePayQueryRequest request) {
        String singlePayQueryPlainText = request.toString();
        log.info("[银联] - [单笔代付查询] - [字符串拼接结果：{}]", singlePayQueryPlainText);
        String plainData = new String(Base64.encode(singlePayQueryPlainText.getBytes()));
        log.info("[银联] - [单笔代付查询] - [转换成Base64后数据结果：{}]", plainData);
        try {
            // 签名
            String chkValue = sign(String.valueOf(request.getMerId()), plainData);
            request.setChkValue(chkValue);
            NameValuePair[] data = {
                    new NameValuePair("merId", request.getMerId()),
                    new NameValuePair("merDate", request.getMerDate()),
                    new NameValuePair("merSeqId", request.getMerSeqId()),
                    new NameValuePair("version", request.getVersion()),
                    new NameValuePair("signFlag", "1"),
                    new NameValuePair("chkValue", chkValue)
            };
            String resMessage = HttpClientUtil.post(request.getSubmitUrl(), data);
            log.info("[银联] - [单笔代付查询] - [返回的响应数据：{}]", resMessage);
            return handleSinglePayQuery(resMessage);
        } catch (Exception e) {
            log.error("[银联] - [单笔代付查询] - [请求银联接口异常]", e);
            throw new InfraException("请求银联接口异常", e);
        }
    }

    private String sign(String merId, String plainData) {
        String chkValue;
        int keyUsage = 0;
        if (StrUtil.isEmpty(unionPayMerPrKString)) {
            throw new InfraException("创建key异常");
        }

        String path = getUnionPayMerPrKPath();
        log.info("商户私钥路径：{}", path);
        PrivateKey key = new PrivateKey();
        boolean flag = key.buildKey(merId, keyUsage, path);
        if (!flag) {
            log.error("[银联] - [创建key失败] - [商户id{}, keyUsage:{}, filePath:{}]", merId, keyUsage, path);
            throw new InfraException("创建key异常");
        } else {
            SecureLink sl = new SecureLink(key);
            chkValue = sl.Sign(plainData);
            log.info("[银联] - [签名内容] - [{}]", chkValue);
        }
        return chkValue;
    }

    private UnionPaySinglePayQueryResponse handleSinglePayQuery(String responseMessage) {
        getIdxAndResponseChk(responseMessage);
        String code = responseMessage.substring(0, 3);
        if (UnionPayConstant.SIN_PAY_QUERY_SUCCESS.equals(code)) {
            String[] split = responseMessage.split("\\|");
            UnionPaySinglePayQueryResponse singlePayQueryResponse = new UnionPaySinglePayQueryResponse();
            singlePayQueryResponse.setResponseCode(split[0]);
            singlePayQueryResponse.setMerId(split[1]);
            singlePayQueryResponse.setMerDate(split[2]);
            singlePayQueryResponse.setMerSeqId(split[3]);
            singlePayQueryResponse.setCpDate(split[4]);
            singlePayQueryResponse.setCpSeqId(split[5]);
            singlePayQueryResponse.setStat(split[14]);
            return singlePayQueryResponse;
        } else if (UnionPayConstant.SIN_PAY_QUERY_NULL.equals(code)) {
            return null;
        } else {
            throw new InfraException(String.format("查询银联出错，请重试！responseMessage：%s", responseMessage));
        }
    }

    private void singlePayResponseChk(String responseMessage) {
        if (StringUtils.isEmpty(responseMessage)) {
            throw new InfraException("银联返回结果为空");
        }
        int idx = responseMessage.lastIndexOf("&");
        String needChkMsg = responseMessage.substring(0, idx);
        int chkValueStart = responseMessage.substring(idx).indexOf("=");
        String responseChkValue = responseMessage.substring(idx + chkValueStart + 1);
        boolean checkResult = responseChk(needChkMsg, responseChkValue);
        if (!checkResult) {
            throw new InfraException("返回结果验签失败");
        }
    }

    private boolean responseChk(String needChkMsg, String responseChkValue) {
        String plainData = new String(Base64.encode(needChkMsg.getBytes()));
        log.info("[银联] - [返回结果验签] - [需要验签的字段：{}]", needChkMsg);
        int keyUsage = 0;
        if (StrUtil.isEmpty(unionPayPgPubKString)) {
            throw new InfraException("创建key异常");
        }

        String path = getUnionPayPgPubKPath();
        log.info("商户公钥路径：{}", path);
        PrivateKey key = new PrivateKey();
        boolean buildKey = key.buildKey("999999999999999", keyUsage, path);
        if (!buildKey) {
            throw new InfraException("创建key异常");
        }

        SecureLink sl = new SecureLink(key);
        return sl.verifyAuthToken(plainData, responseChkValue);
    }

    public UnionPaySinglePayResponse singlePay(UnionPaySinglePayRequest request) {
        String singlePayPlainText = request.toString();
        log.info("[银联] - [单笔代付] - [字符串拼接结果：{}]", singlePayPlainText);
        String plainData = new String(Base64.encode(singlePayPlainText.getBytes()));
        log.info("[银联] - [单笔代付] - [转换成Base64后数据结果：{}]", plainData);
        try {
            // 签名
            String chkValue = sign(String.valueOf(request.getMerId()), plainData);
            request.setChkValue(chkValue);
            //填入各个表单域的值
            NameValuePair[] data = {
                    new NameValuePair("merId", request.getMerId()),
                    new NameValuePair("merDate", request.getMerDate()),
                    new NameValuePair("merSeqId", request.getMerSeqId()),
                    new NameValuePair("cardNo", request.getCardNo()),
                    new NameValuePair("usrName", request.getUsrName()),
                    new NameValuePair("openBank", request.getOpenBank()),
                    new NameValuePair("prov", request.getProv()),
                    new NameValuePair("city", request.getCity()),
                    new NameValuePair("transAmt", String.valueOf(request.getTransAmt())),
                    new NameValuePair("purpose", request.getPurpose()),
                    new NameValuePair("subBank", request.getSubBank()),
                    new NameValuePair("flag", request.getFlag()),
                    new NameValuePair("version", request.getVersion()),
                    new NameValuePair("chkValue", chkValue),
                    new NameValuePair("termType", request.getTermType()),
                    new NameValuePair("payMode", request.getPayMode()),
                    new NameValuePair("signFlag", request.getSignFlag())
            };
            String resMessage = HttpClientUtil.post(request.getSubmitUrl(), data);
            log.info("[银联] - [单笔代付] - [返回的响应数据：{}]", resMessage);
            return handleSinglePayResponse(resMessage);
        } catch (Exception e) {
            log.error("[银联] - [单笔代付] - [请求银联接口异常]", e);
            throw new InfraException("请求银联接口异常", e);
        }
    }

    /**
     * 处理单笔代付响应数据
     */
    private UnionPaySinglePayResponse handleSinglePayResponse(String responseMessage) {
        singlePayResponseChk(responseMessage);
        List<org.apache.http.NameValuePair> responsePairs = URLEncodedUtils.parse(responseMessage, StandardCharsets.UTF_8);
        Map<String, String> resultMap = new HashMap<>(responsePairs.size());
        responsePairs.forEach(vp -> resultMap.put(vp.getName(), vp.getValue()));
        UnionPaySinglePayResponse unionPaySinglePayResponse = BeanUtil.fillBeanWithMap(resultMap, new UnionPaySinglePayResponse(), false);
        unionPaySinglePayResponse.setMessage(UnicodeUtil.toString(unionPaySinglePayResponse.getMessage()));
        return unionPaySinglePayResponse;
    }

    public UnionPayAmountResponse queryMerchantAmount(UnionPayAmountRequest request) {
        String queryMerchantAmountPlainText = request.toString();
        log.info("[银联] - [商户余额查询] - [字符串拼接结果：{}]", queryMerchantAmountPlainText);
        String plainData = new String(Base64.encode(queryMerchantAmountPlainText.getBytes()));
        log.info("[银联] - [商户余额查询] - [转换成Base64后数据结果：{}]", plainData);
        try {
            // 签名
            String chkValue = sign(String.valueOf(request.getMerId()), plainData);
            request.setChkValue(chkValue);
            NameValuePair[] data = {
                    new NameValuePair("merId", request.getMerId()),
                    new NameValuePair("version", request.getVersion()),
                    new NameValuePair("signFlag", "1"),
                    new NameValuePair("chkValue", chkValue)
            };

            String resMessage = HttpClientUtil.post(request.getBalanceQueryUrl(), data);
            log.info("[银联] - [商户余额查询] - [返回的响应数据：{}]", resMessage);
            return handleMerchantAmountResponse(resMessage);
        } catch (Exception e) {
            log.error("[银联] - [商户余额查询] - [请求银联接口异常]", e);
            throw new InfraException("请求银联接口异常", e);
        }
    }

    private UnionPayAmountResponse handleMerchantAmountResponse(String responseMessage) {
        int idx = getIdxAndResponseChk(responseMessage);
        String code = responseMessage.substring(0, 3);
        UnionPayAmountResponse amountResponse = new UnionPayAmountResponse();
        amountResponse.setCode(code);
        if (UnionPayConstant.MERCHANT_AMOUNT_QUERY.equals(code)) {
            String merId = responseMessage.substring(4, 19);
            String merAmt = responseMessage.substring(20, idx);
            amountResponse.setMerId(merId);
            amountResponse.setMerAmt(merAmt);
        }
        return amountResponse;
    }

    private int getIdxAndResponseChk(String responseMessage) {
        if (StringUtils.isEmpty(responseMessage)) {
            throw new InfraException("银联返回结果为空");
        }
        int idx = responseMessage.lastIndexOf("|");
        String chkValue = responseMessage.substring(idx + 1);
        String plainData = responseMessage.substring(0, idx + 1);
        boolean checkResult = responseChk(plainData, chkValue);
        if (!checkResult) {
            throw new InfraException("返回结果验签失败");
        }
        return idx;
    }

    public String getUnionPayMerPrKPath() {
        return getUnionPayKeyPath(unionPayMerPrKPath, unionPayMerPrKName, unionPayMerPrKString);
    }

    public String getUnionPayPgPubKPath() {
        return getUnionPayKeyPath(unionPayPgPubKPath, unionPayPgPubKName, unionPayPgPubKString);
    }

    private String getUnionPayKeyPath(String unionPayKeyPath, String unionPayKeyName, String unionPayKeyString) {
        try {
            File filePath = new File(unionPayKeyPath);
            if (!filePath.exists()) {
                filePath.mkdirs();
            }
            File file = new File(unionPayKeyPath + File.separator + unionPayKeyName);
            if (!file.exists()) {
                file.createNewFile();
            }
            FileUtil.string2File(unionPayKeyString, file);
            return unionPayKeyPath + File.separator + unionPayKeyName;
        } catch (Exception e) {
            log.error("string2File error", e);
            throw new InfraException("string2File error", e);
        }
    }
}
